import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Properties;

/**
 * This class defines the GUI for the Crib MSC application.
 * 
 * TODO v2 Extend support to multiple languages.
 * TODO v2 Enable specifying of characters.
 * TODO v2 Statistics tab.
 * TODO v2 Redesign and make the options pane.
 * TODO v2 Make Save/Open Project apply to stats.
 * TODO v2 Review IOC calculation. Compare to SCB.
 * TODO v3 Auto-detect which language.
 * TODO v3 Make file loaders remember last used directory.
 * TODO v3 Include file associations with .crib.
 * TODO v3 Consider change to pre-building of  tree.
 * TODO v3 Allow input to be received using other chars to A-Z
 * TODO v3 Splash screen.
 * TODO v3 Use bi-grams or tri-grams to eliminate need of word spaces.
 * TODO v3 Grammatical analysis of results to find most likely correct.
 * TODO v3 Switch to SWT.
 * TODO v3 Rearranging of output tab - Solutions, Statistics tabs.
 * 
 * @author Tom Castle
 * @version v.2.01 12/11/04
 */
public class Crib
{
	public static void main(String[] args)
	{
	    Crib UserInterface = new Crib();
	}

    //CONSTANTS
    private static final String VERSION = "2.31";
    public static final String SETTINGSFILE = "settings.ini";
    private SwingWorker cracker;
    private SwingWorker timer;
    private Crack crackEngine;
    private int centreX,centreY;
    private long timeTaken; //time taken for last crack.
    private Timer timerTotal;

    
    //VARIABLES
    //GUI Components.
    private JFrame frame;
    private JTabbedPane tabbedPane;
    private Container centerPane,inputTab,outputTab,statsTab,contentPane,
                inputTabTop;
    private JPanel outputTabTop,outputTabBot,outputTabBotTop,outputTabBotBtns,stats;
    private JDialog opFrame; 
    private JMenuBar menubar;
    private JMenu fileMenu, crackMenu, toolsMenu, helpMenu;
    private JMenuItem newItem,openItem,saveItem,loadCTItem,savePTItem,
                printPTItem,quitItem,startCrackItem,stopCrackItem,optionsItem,
                helpItem,aboutItem;
    private JToolBar toolbar;
    private ImageIcon newIcon,saveIcon,loadIcon,crackIcon,stopIcon,helpIcon,
                aboutIcon,quitIcon;
    private JButton startBtn,stopBtn,btnBrowse,newProjBtn,saveProjBtn,loadProjBtn,
                helpBtn,aboutBtn,quitBtn,savePTBtn,printPTBtn;
    private JCheckBox chkMinScoring,chkSelfMapping;
    private JTextField txtLoadFile,txtDictionaryFile;
    private JTextArea txtCiphertext,txtPlaintext;
    private JScrollPane logPane,ctScrollPane,ptScrollPane,altSolPane;
    private JSlider sldDictProp;
    private JList logList,altSolList;
    private DefaultListModel logModel,altSolModel;
    private JLabel lblAltSol,lblEstimatedScore,lblActualScore,lblIndexCoincidence,
                lblLanguage,lblTime, statusBar;

    /**
     * Create instance of CribGui and show on screen.
     */
    public Crib()
    {
        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
        centreX = (int)(dim.getWidth())/2;
        centreY = (int)(dim.getHeight())/2;
        timeTaken = 0;
        makeFrame();
        crackEngine = new Crack();
    }

//***************************************
//********GUI CONSTRUCTION BELOW*********
//***************************************

    /**
     * Create the swing frame and its content.
     */
    private void makeFrame()
    {
        try {
        	UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }catch(Exception e){
            //System.out.println("Unable to load native look and feel");
        }
        
        frame = new JFrame("Crib " + VERSION);
        //frame.setResizable(false);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        Container contentPane = (JPanel) frame.getContentPane();
        contentPane.setLayout(new BorderLayout());

        makeToolBar(frame);
        makeMenuBar(frame);
        //Create panel for tabs and log win.
        centerPane = new JPanel();
        centerPane.setLayout(new BorderLayout());
        contentPane.add(centerPane,BorderLayout.CENTER);        
        makeTabs(frame);
        //makeOptionsPane();
        
        //Make log window.
        logModel = new DefaultListModel();
        logList = new JList(logModel);
        logList.setFont(new Font("ListFont",Font.PLAIN,10));
        logList.setVisibleRowCount(4);
        logPane = new JScrollPane(logList);
        centerPane.add(logPane,BorderLayout.SOUTH);
        
        //Make Status Bar.
        statusBar = new JLabel(" ");
        contentPane.add(statusBar,BorderLayout.SOUTH);
        
        //Complete frame.
        frame.pack();
        //calculate the x,y co-ords for the top-left corner.
        int frameX = centreX-(frame.getWidth()/2);
        int frameY = centreY-(frame.getHeight()/2);
        frame.setLocation(frameX,frameY);
        //make visible.
        frame.setVisible(true);
    }
        
    /**
     * Build the menu bar.
     */
    private void makeMenuBar(JFrame frame)
    {
        menubar = new JMenuBar();
        frame.setJMenuBar(menubar);
        
        //FILE MENU
        fileMenu = new JMenu("File");
        menubar.add(fileMenu);
        //new menuitem
        newItem = new JMenuItem("New Project");
        newItem.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){newProject();}});
        fileMenu.add(newItem);
        //open menuitem
        openItem = new JMenuItem("Load Project");
        openItem.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){openProject();}});
        fileMenu.add(openItem);
        //save menuitem
        saveItem = new JMenuItem("Save Project");
            saveItem.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){saveProject();}});
        fileMenu.add(saveItem);
        //separator
        fileMenu.addSeparator();
        //load ciphertext menuitem
        loadCTItem = new JMenuItem("Load Ciphertext");
        loadCTItem.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){loadCTFile();}});
        fileMenu.add(loadCTItem);
        //save plaintext menuitem
        savePTItem = new JMenuItem("Save Plaintext");
        savePTItem.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){savePlaintext();}});
        fileMenu.add(savePTItem);
        //print plaintext menuitem
        printPTItem = new JMenuItem("Print Plaintext");
        fileMenu.add(printPTItem);
        //separator
        fileMenu.addSeparator();
        //quit menuitem
        quitItem = new JMenuItem("Quit");
        quitItem.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){quit();}});
        fileMenu.add(quitItem);
        
        //CRACK MENU
        crackMenu = new JMenu("Crack");
        menubar.add(crackMenu);
        
        startCrackItem = new JMenuItem("Start Crack");
        startCrackItem.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){startCrack();}});
        crackMenu.add(startCrackItem);
        
        stopCrackItem = new JMenuItem("Stop Crack");
        stopCrackItem.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){stopCrack();}});
        stopCrackItem.setEnabled(false);
        crackMenu.add(stopCrackItem);
        
        //TOOLS MENU
        toolsMenu = new JMenu("Tools");
        menubar.add(toolsMenu);

        optionsItem = new JMenuItem("Options");
        optionsItem.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){options();}});
        toolsMenu.add(optionsItem);        
        
        //GLUE TO PUSH ELEMENTS RIGHT
        menubar.add(Box.createHorizontalGlue());
        
        //HELP MENU
        helpMenu = new JMenu("Help");
        menubar.add(helpMenu);

        helpItem = new JMenuItem("Crib Help");
        helpMenu.add(helpItem);
        
        aboutItem = new JMenuItem("About Crib");
        aboutItem.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){about();}});
        helpMenu.add(aboutItem);        
    }

    /**
     * Build the tool bar.
     */
    private void makeToolBar(JFrame frame)
    {
        contentPane = (JPanel) frame.getContentPane();
        
        //MAKE TOOLBAR
        toolbar = new JToolBar();
        contentPane.add(toolbar,BorderLayout.NORTH);
        
        //ADD TOOLBAR BUTTONS
        //PROJECT BUTTONS
        //New Project
        newIcon = new ImageIcon("images/new.gif");
        newProjBtn = new JButton(newIcon);
        newProjBtn.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){newProject();}});
        newProjBtn.setToolTipText("New Project");
        toolbar.add(newProjBtn);
        //Save Project
        saveIcon = new ImageIcon("images/save.gif");
        saveProjBtn = new JButton(saveIcon);
        saveProjBtn.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){saveProject();}});
        saveProjBtn.setToolTipText("Save Project");
        toolbar.add(saveProjBtn);        
        //Load Project
        loadIcon = new ImageIcon("images/open.gif");
        loadProjBtn = new JButton(loadIcon);
        loadProjBtn.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){openProject();}});
        loadProjBtn.setToolTipText("Load Project");
        toolbar.add(loadProjBtn);
        //Seperator
        toolbar.addSeparator();
        
        //CRACK BUTTONS
        //Start Crack
        crackIcon = new ImageIcon("images/crack.gif");
        startBtn = new JButton(crackIcon);
        startBtn.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){startCrack();}});
        startBtn.setToolTipText("Start Crack");
        toolbar.add(startBtn);
        //Stop Crack
        stopIcon = new ImageIcon("images/stop.gif");
        stopBtn = new JButton(stopIcon);
        stopBtn.setEnabled(false);
        stopBtn.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){stopCrack();}});
        stopBtn.setToolTipText("Stop Crack");
        toolbar.add(stopBtn);
        //Seperator
        toolbar.addSeparator();
        
        //EXTRA BUTTONS
        //Help
        helpIcon = new ImageIcon("images/help.gif");
        helpBtn = new JButton(helpIcon);
        helpBtn.setToolTipText("Get Help");
        toolbar.add(helpBtn);
        //About
        aboutIcon = new ImageIcon("images/about.gif");
        aboutBtn = new JButton(aboutIcon);
        aboutBtn.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){about();}});
        aboutBtn.setToolTipText("About Crib");
        toolbar.add(aboutBtn);
        //GLUE TO PUSH ELEMENTS LEFT
    }

    /**
     * Build tabs
     */
    private void makeTabs(JFrame frame)
    { 
        tabbedPane = new JTabbedPane();
        centerPane.add(tabbedPane,BorderLayout.CENTER);
        
        //INPUT TAB PANEL
        inputTab = new JPanel(new BorderLayout());
        inputTabTop = new JPanel(new FlowLayout(FlowLayout.LEFT));
        inputTab.add(inputTabTop,BorderLayout.NORTH);
        //Add label ("load from file")
        inputTabTop.add(new JLabel("Load from file:"));
        //Add textfield
        txtLoadFile = new JTextField(22);
        txtLoadFile.setFont(new Font("text",Font.PLAIN,10));
        txtLoadFile.setEnabled(false);
        txtLoadFile.setBackground(Color.LIGHT_GRAY);
        inputTabTop.add(txtLoadFile);
        //Add browse button
        btnBrowse = new JButton("Browse");
        btnBrowse.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){loadCTFile();}});
        inputTabTop.add(btnBrowse);
        //Add label ("or type below")
        inputTabTop.add(new JLabel("(or type below)"));
        //Strut to expand window to desired size.
        inputTabTop.add(Box.createHorizontalStrut(100));
        //Add ciphertext textarea
        txtCiphertext = new JTextArea(15,50);
        txtCiphertext.setLineWrap(true);
        txtCiphertext.setWrapStyleWord(true);
        ctScrollPane = new JScrollPane(txtCiphertext);
        inputTab.add(ctScrollPane,BorderLayout.CENTER);
        
        //OUTPUT TAB PANEL
        outputTab = new JPanel(new GridLayout(2,1));
        //*Build top pane
        outputTabTop = new JPanel(new BorderLayout());
        outputTab.add(outputTabTop);
        //Add label ("Plaintext Result")
        outputTabTop.add(new JLabel(" Plaintext Result:"),BorderLayout.NORTH);
        //Add plaintext textarea
        txtPlaintext = new JTextArea();
        txtPlaintext.setLineWrap(true);
        txtPlaintext.setWrapStyleWord(true);
        ptScrollPane = new JScrollPane(txtPlaintext);
        outputTabTop.add(ptScrollPane,BorderLayout.CENTER);
        //*Build bottom pane
        outputTabBot = new JPanel(new GridLayout(2,1));
        outputTabBotTop = new JPanel(new GridLayout(2,1));
        outputTabBotBtns = new JPanel(new FlowLayout(FlowLayout.LEFT));
        //Add buttons
        savePTBtn = new JButton("Save Plaintext");
        savePTBtn.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){savePlaintext();}});        
        outputTabBotBtns.add(savePTBtn);
        printPTBtn = new JButton("Print Plaintext");
        outputTabBotBtns.add(printPTBtn);
        outputTabBotTop.add(outputTabBotBtns);
        //Add label ("Alternative Solutions")
        lblAltSol = new JLabel(" Alternative Solutions:");
        lblAltSol.setVerticalAlignment(JLabel.BOTTOM);
        outputTabBotTop.add(lblAltSol);
        outputTabBot.add(outputTabBotTop);
        //Add alt solutions list
        altSolModel = new DefaultListModel();
        altSolList = new JList(altSolModel);
        altSolList.addListSelectionListener(new ListSelectionListener(){
            public void valueChanged(ListSelectionEvent e){changeSolution(altSolList.getSelectedIndex());}});             
        altSolList.setFont(new Font("ListFont",Font.PLAIN,10));
        altSolList.setVisibleRowCount(4);
        altSolPane = new JScrollPane(altSolList);
        outputTabBot.add(altSolPane);
        outputTab.add(outputTabBot);
        
        //STATISTICS TAB PANEL
        statsTab = new JPanel(new BorderLayout());
        stats = new JPanel(new GridLayout(5,3));
        stats.add(new JLabel(" Estimate score: "));
        lblEstimatedScore = new JLabel();
        stats.add(lblEstimatedScore);
        stats.add(Box.createHorizontalStrut(100));
        stats.add(new JLabel(" Actual score: "));
        lblActualScore = new JLabel();
        stats.add(lblActualScore);
        stats.add(Box.createHorizontalStrut(100));
        stats.add(new JLabel(" Index of coincidence: "));
        lblIndexCoincidence = new JLabel();
        stats.add(lblIndexCoincidence);
        stats.add(Box.createHorizontalStrut(100));
        stats.add(new JLabel(" Probable language: "));
        lblLanguage = new JLabel();
        stats.add(lblLanguage);
        stats.add(Box.createHorizontalStrut(100));
        stats.add(new JLabel(" Time taken: "));
        lblTime = new JLabel();
        stats.add(lblTime);
        stats.add(Box.createHorizontalStrut(100));
        statsTab.add(stats,BorderLayout.NORTH);
        
        tabbedPane.addTab("Input", inputTab);
        tabbedPane.addTab("Output", outputTab);
        tabbedPane.addTab("Stats", statsTab);
    }
    
    /**
     * Create JFrame for Options window.
     */
    private void makeOptionsPane()
    {
        opFrame = new JDialog(frame, "Options", true);
        opFrame.setResizable(false);
        
        opFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        
        JPanel opContentPane = (JPanel) opFrame.getContentPane();
        opContentPane.setLayout(new BorderLayout());
        
        JPanel optionsPane = new JPanel(new GridLayout(2,1));
        
        //get starting properties.
        String dictFilePath = "";
        double dictProportion = 45;
        boolean useEstimate = false;
        boolean selfMapping = true;
        try{
            Properties p = new Properties();
            p.load(new FileInputStream(new File(SETTINGSFILE)));
            dictFilePath = p.getProperty("Dictionary","English.txt");
            dictProportion = ((new Double(p.getProperty("ScoreEstimateProportion","0.45"))).doubleValue())*100;
            useEstimate = ((p.getProperty("ScoreEstimation","False")).equals("True"));
            selfMapping = ((p.getProperty("SelfMappingValid","True")).equals("True"));

            //int wordsInStart = (new Integer(p.getProperty("WordsInStart","40"))).intValue();
        }catch(IOException e){
            //error - try to continue with default values, NEED ASSIGNING.
        }
        //DICTIONARY PANE
        JPanel dictionaryPane = new JPanel(new GridLayout(2,1));
        dictionaryPane.setBorder(BorderFactory.createTitledBorder("Dictionary"));
        
        JPanel dictRow1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
        txtDictionaryFile = new JTextField(20);
        txtDictionaryFile.setText(dictFilePath);
        JButton btnDictBrowse = new JButton("Browse");
                btnDictBrowse.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){loadDictFile();}});
        dictRow1.add(new JLabel("Dictionary:"));
        dictRow1.add(txtDictionaryFile);
        dictRow1.add(btnDictBrowse);
        
        JPanel dictRow2 = new JPanel(new FlowLayout(FlowLayout.LEFT));
        final JTextField txtDictProp = new JTextField(3);
        txtDictProp.setEnabled(false);
        sldDictProp = new JSlider(0,1);
            sldDictProp.addChangeListener(new ChangeListener(){
            public void stateChanged(ChangeEvent e){txtDictProp.setText(sldDictProp.getValue()+"%");}});
        sldDictProp.setMaximum(100);
        sldDictProp.setMajorTickSpacing(5);
        //sldDictProp.setSnapToTicks(true);
        sldDictProp.setValue((int)dictProportion);
        dictRow2.add(new JLabel("Proportion:"));
        dictRow2.add(sldDictProp);
        dictRow2.add(txtDictProp);
        
        dictionaryPane.add(dictRow1);
        dictionaryPane.add(dictRow2);
        
        //OPERATIONS PANE
        JPanel operationsPane = new JPanel(new GridLayout(2,1));
        operationsPane.setBorder(BorderFactory.createTitledBorder("Operations"));
        
        chkMinScoring = new JCheckBox("Use minimum score pruning?",useEstimate);
        chkSelfMapping = new JCheckBox("Self mapping letters valid?",selfMapping);
        operationsPane.add(chkMinScoring);
        operationsPane.add(chkSelfMapping);
        
        //BUTTONS PANE
        JPanel buttonsPane = new JPanel(new FlowLayout(FlowLayout.LEFT));
        
        JButton btnOk = new JButton("Ok");
            btnOk.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){updateSettings();closeOptionsPane();}});
        JButton btnCancel = new JButton("Cancel");
            btnCancel.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){closeOptionsPane();}});
        
        buttonsPane.add(btnOk);
        buttonsPane.add(btnCancel);
        
        optionsPane.add(dictionaryPane);
        optionsPane.add(operationsPane);
        
        opContentPane.add(optionsPane,BorderLayout.CENTER);
        opContentPane.add(buttonsPane,BorderLayout.SOUTH);
        
        opFrame.pack();
        int opFrameX = centreX-(opFrame.getWidth()/2);
        int opFrameY = centreY-(opFrame.getHeight()/2);
        opFrame.setLocation(opFrameX,opFrameY);
        opFrame.setVisible(true);
    }

//***************************************
//********BUTTON FUNCTIONS BELOW*********
//***************************************

    /**
     * New project function: clear everything for new project.
     */
    private boolean newProject()
    {
        JOptionPane confirm = new JOptionPane();
        if(isContentLess() || JOptionPane.showOptionDialog(frame, 
            "All unsaved data will be lost, continue?",
            "Confirm Project",
            JOptionPane.YES_NO_OPTION,
            JOptionPane.QUESTION_MESSAGE,
            new ImageIcon("images/question.gif"),
            null,
            null) == 0){
                logModel.clear();
                txtLoadFile.setText("");
                txtCiphertext.setText("");
                clearOutput();
                return true;
        }else{
            return false;
        }
    }

    /**
     * Open project function: open a saved project for continuation.
     */
    private void openProject()
    {
        JFileChooser chooser = new JFileChooser("Projects/");
        CustomFileFilter filter = new CustomFileFilter();
        filter.addExtension("crib");
        chooser.setFileFilter(filter);
        chooser.setAcceptAllFileFilterUsed(false);
        int returnVal = chooser.showOpenDialog(frame);
        if(returnVal == JFileChooser.APPROVE_OPTION){  
            File file = chooser.getSelectedFile();
            try{
                Properties p = new Properties();
                p.load(new FileInputStream(file));
                //clear current project
                if (isContentLess() || newProject()){
                    //retrieve saved data.
                    String lCiphertext = p.getProperty("Ciphertext","");
                    String lPlaintext = p.getProperty("Plaintext","");
                    String lCiphertextFile = p.getProperty("CiphertextFile","");
                    String lLog = p.getProperty("Log","");
                    String lAltSols = p.getProperty("AltSolutions","");
                    //insert loaded data.
                    txtCiphertext.setText(lCiphertext);
                    txtPlaintext.setText(lPlaintext);
                    txtLoadFile.setText(lCiphertextFile);
                    StringTokenizer st1 = new StringTokenizer(lLog,";");
                    while(st1.hasMoreTokens()){
                        appendLog(st1.nextToken());
                    }
                    StringTokenizer st2 = new StringTokenizer(lAltSols,";");
                    while(st2.hasMoreTokens()){
                        altSolModel.addElement(st2.nextToken());
                    }
                }
            }catch(IOException e){
                //error - try and continue with default settings.
            }
        }
    }
    
    /**
     * Save project function: save a project for continuation.
     */
    private void saveProject()
    {
        JFileChooser chooser = new JFileChooser("Projects/");
        CustomFileFilter filter = new CustomFileFilter();
        filter.addExtension("crib");
        chooser.setFileFilter(filter);
        chooser.setAcceptAllFileFilterUsed(false);
        int returnVal = chooser.showSaveDialog(frame);
        if(returnVal == JFileChooser.APPROVE_OPTION){              
            File tempFile = chooser.getSelectedFile();
            String tempFilePath = tempFile.getPath();
            String ext = filter.getExtension(tempFile);
            if (ext == null || !ext.equals("crib"))
                tempFilePath = tempFilePath + ".crib";
            File file = new File(tempFilePath);
            if(!file.exists() || (JOptionPane.showOptionDialog(frame, 
                                    "File already exists, would you like to overwrite?",
                                    "Confirm Save Project",
                                    JOptionPane.YES_NO_OPTION,
                                    JOptionPane.QUESTION_MESSAGE,
                                    new ImageIcon("images/question.gif"),
                                    null, null) == 0)){              
                                        //write to file
                                        try{
                                            Properties p = new Properties();
                                            p.setProperty("CiphertextFile",txtLoadFile.getText());
                                            p.setProperty("Ciphertext",txtCiphertext.getText());
                                            p.setProperty("Plaintext",txtPlaintext.getText());
                                            //read in alternative solutions.
                                            String altSolutions = "";
                                            ListModel altSolutionsModel = altSolList.getModel();
                                            int altSize = altSolutionsModel.getSize();
                                            for(int i=0;i<altSize;i++)
                                                altSolutions = altSolutions + ((String) altSolutionsModel.getElementAt(i)) + ';';
                                            p.setProperty("AltSolutions",altSolutions);
                                            //read in log items.
                                            String log = "";
                                            ListModel logModel = logList.getModel();
                                            int logSize = logModel.getSize();
                                            for(int i=0;i<logSize;i++)
                                                log = log + ((String) logModel.getElementAt(i)) + ";";
                                            p.setProperty("Log",log); //read each entry in and seperate by a colon.
                                            p.store(new FileOutputStream(file),null);
                                            //create copy of current settings file.
                                        }catch(IOException e){
                                            appendLog("Unable to save project to file: " + file.getName());
                                        }

            }        
        }
    }
    
    /**
     * Options function: open the options JFrame.
     */
    private void options()
    {
        makeOptionsPane();
        //opFrame.setVisible(true);
    }
    
    /**
     * Close the options pane.
     */
    private void closeOptionsPane()
    {
        //opFrame.setVisible(false);
        opFrame.dispose();
    }
    
    /**
     * Quit function: quit the application.
     */
    public void quit()
    {
        System.exit(0);
    }
    
    /**
     * 'About' function: show the 'about' box.
     */
    private void about()
    {
        JOptionPane.showMessageDialog(frame, 
                    "Crib: " + VERSION + "\nAuthor: Xenon",
                    "About Crib", 
                    JOptionPane.INFORMATION_MESSAGE);
    }
    
    /**
     * Save Plaintext function: Save the text currently held in the 
     * plaintext textarea.
     */
    private void savePlaintext()
    {
        JFileChooser chooser = new JFileChooser();
        CustomFileFilter filter = new CustomFileFilter();
        filter.addExtension("txt");
        chooser.setFileFilter(filter);
        int returnVal = chooser.showSaveDialog(frame);
        if(returnVal == JFileChooser.APPROVE_OPTION){  
            File file = chooser.getSelectedFile();
            if(!file.exists() || (JOptionPane.showOptionDialog(frame, 
                                    "File already exists, would you like to overwrite?",
                                    "Confirm Save Plaintext",
                                    JOptionPane.YES_NO_OPTION,
                                    JOptionPane.QUESTION_MESSAGE,
                                    new ImageIcon("images/question.gif"),
                                    null, null) == 0)){              
                                        try {
                                            String PT = txtPlaintext.getText();
                                            FileWriter writer = new FileWriter(file);
                                            writer.write(PT);
                                            writer.close();
                                            //txtLoadFile.setText(file.getPath());
                                            appendLog("Plaintext successfully saved to file: " + file.getName());
                                        }
                                        catch(IOException e){
                                            appendLog("Unable to save to file: " + file.getName());
                                        }
            }        
        }
    }
    
    /**
     * Load from file ('Browse') function: get filename.
     */
    private void loadCTFile()
    {
        JFileChooser chooser = new JFileChooser("ExampleTexts/");
        CustomFileFilter filter = new CustomFileFilter();
        filter.addExtension("txt");
        chooser.setFileFilter(filter);
        int returnVal = chooser.showOpenDialog(frame);
        if(returnVal == JFileChooser.APPROVE_OPTION){  
            File file = chooser.getSelectedFile();
            try {
                BufferedReader reader = new BufferedReader(new FileReader(file));
                //for each line of the text file, read and insert into textarea.
                String ct = "";
                while(reader.ready()){
                    ct += reader.readLine();
                }
                txtCiphertext.setText(ct);
                clearOutput();
                reader.close();
                txtLoadFile.setText(file.getPath());
                appendLog("Ciphertext successfully loaded from file: " + file.getName());
            }
            catch(IOException e){
                appendLog("Unable to open file: " + file.getName());
            }
        }
    }
    
    /**
     * Load dictionary from file ('Browse') function: get filename.
     */
     private void loadDictFile()
     {
         JFileChooser chooser = new JFileChooser("Dictionaries/");
        CustomFileFilter filter = new CustomFileFilter();
        filter.addExtension("txt");
        chooser.setFileFilter(filter);
        //chooser.setAcceptAllFileFilterUsed(false);
        int returnVal = chooser.showOpenDialog(frame);
        if(returnVal == JFileChooser.APPROVE_OPTION){  
            File file = chooser.getSelectedFile();
            txtDictionaryFile.setText(file.getPath());
        }
    }
    
    /**
     * Update settings.
     */
    private void updateSettings()
    {
        //Dictionary file.
        String filePath = txtDictionaryFile.getText();
        Dictionary dict = new Dictionary(filePath);
        //appendLog("Loading new dictionary... " + (new File(filePath)).getName());
        if (crackEngine!=null) crackEngine.setDictionary(dict);
        //Proportion
        double dictPropEstimate = sldDictProp.getValue();
        String minScoring = (chkMinScoring.isSelected()) ? "True" : "False";
        String selfMapping = (chkSelfMapping.isSelected()) ? "True" : "False";
        
        //write to ini
        try{
            Properties p = new Properties();
            p.load(new FileInputStream(new File("settings.ini")));
            p.setProperty("Dictionary",filePath);
            p.setProperty("ScoreEstimateProportion",(dictPropEstimate/100) + "");
            p.setProperty("ScoreEstimation",minScoring);
            p.setProperty("SelfMappingValid",selfMapping);
            //wordsInStart = (new Integer(p.getProperty("WordsInStart","40"))).intValue();
            p.store(new FileOutputStream(new File("settings.ini")),null);
        }catch(IOException e){
            //error - try to continue with default settings.
        }
    }
    
    /**
     * Change the solution displayed in the output text area.
     * 
     * @param   the index of solution from JList altSolList to change to.
     */
    private void changeSolution(int index)
    {
        if (index != -1) txtPlaintext.setText((String) altSolModel.getElementAt(index));
    }
    
    /**
     * Start crack function: starts the cracking engine.
     */
    private void startCrack()
    {
        startBtn.setEnabled(false);
        stopBtn.setEnabled(true);
        startCrackItem.setEnabled(true);
        stopCrackItem.setEnabled(false);
        //clear output panel.
        clearOutput();
        //crackEngine = new Crack();
        crackEngine.clean();
        cracker = new SwingWorker() {
            public Object construct() {
                if (txtCiphertext.getText().equals("")){
                    appendLog("No ciphertext loaded.");
                }else{
                    crackEngine.setCipherText(txtCiphertext.getText());
                    appendLog("Crack engine loaded.");
                    if (usingScoreEstimation(crackEngine))
                        appendLog("Estimated score: " + crackEngine.findScoreEstimate());
                    appendLog("Solving...");
                    //timing variables
                    long tStart = 0;
                    timeTaken = 0; 
                    try{
                        runTimerTotal();
                        tStart = System.currentTimeMillis();
                        crackEngine.solve();
                        appendLog("Filling gaps in decryption key...");
                        crackEngine.fillGaps();
                        timeTaken = System.currentTimeMillis() - tStart;
                    }
                    catch (InterruptedException e){
                        timeTaken = System.currentTimeMillis() - tStart;
                        appendLog("Cracking stopped!");
                        output(crackEngine.getBestPerms(), crackEngine);
                        appendLog("Best results found displayed.");
                    }
                    appendLog("Finished in " + convertTimeToString(timeTaken,true) + "!");
                    appendLog(crackEngine.getBestPerms().size() + " possible solution(s) found!");
                    output(crackEngine.getBestPerms(), crackEngine);
                }
                return null;
                
            }
            public void finished() {
                startBtn.setEnabled(true);
                stopBtn.setEnabled(false);
                startCrackItem.setEnabled(true);
                stopCrackItem.setEnabled(false);
                stopTimerTotal();
            }
        };
        cracker.start();  //required for SwingWorker 3
    }

    /**
     * Calls interrupt() on cracker thread.
     */
    private void stopCrack()
    {
        cracker.interrupt();
    }
    
//***************************************
//*********OTHER FUNCTIONS BELOW*********
//***************************************

    /**
     * Convert a milliseconds time to hrs, mins, secs string.
     * format: "x hrs y mins z secs" if textual true
     * or: "xx:yy:zz" if textual false
     * Excluding any units that are 0.
     */
    private String convertTimeToString(long time,boolean textual)
    {
        double hours = time/3600000;
        long mins = (time % 3600000)/60000;
        long secs = (time % 60000)/1000;
        long mili = (time % 1000);
        String sHours = (hours < 10)? ("0"+hours) : (""+hours);
        String sMins = (mins < 10)? ("0"+mins) : (""+mins);
        String sSecs = (secs < 10)? ("0"+secs) : (""+secs);
        String sMili = (mili < 100)? ((mili<10)? ("00"+mili) : ("0"+mili)) : (""+mili);
        if (textual){
            if (hours != 0)
                return sHours + " hrs, " + sMins + " mins, " + sSecs + " secs";
            if (mins != 0)
                return sMins + " mins, " + sSecs + " secs, " + sMili + " msecs";
            if (secs != 0)
                return sSecs + " secs, " + sMili + " msecs";
            else
                return sMili + " msecs";
        }else{
            if (hours != 0)
                return sHours + " : " + sMins + " : " + sSecs + " (h : m : s)";
            else
                return sMins + " : " + sSecs + " : " + sMili + " (m : s : ms)";
        }
    }

    /**
     * Append to log list.
     */
    public void appendLog(String newLog)
    {
        logModel.addElement(newLog);
        frame.pack();
        logList.ensureIndexIsVisible(logModel.getSize()-1);
        logList.setSelectedIndex(logModel.getSize()-1);
    }
    
    public void runTimerTotal()
    {
        Action updateTimerAction = new AbstractAction() {
            private static final long serialVersionUID = 1L;//required.
            long tStart = System.currentTimeMillis();
            long tPast;
            public void actionPerformed(ActionEvent e) {
                tPast = System.currentTimeMillis()-tStart;
                setStatusBar("Total Time: " + convertTimeToString(tPast,false));
            }
        };

        timerTotal = new Timer(123, updateTimerAction);
        timerTotal.start();
	}

    /**
     * Stop the timer if currently running.
     * Returns true if was running else false.
     */
    public boolean stopTimerTotal()
    {
        if(timerTotal == null) return false;
        boolean running = timerTotal.isRunning();
        if (running){
            timerTotal.stop();
            setStatusBar("");
        }
        return running;
    }
    
    /**
     * Sets the status bar with a space at the front.
     */
    public void setStatusBar(String s)
    {
        statusBar.setText(" " + s);
    }
    
    /**
     * Generate output from the perms given.
     */
    private void output(ArrayList perms, Crack crackEngine)
    {            
        //clear output alternative solution list.
        altSolModel.clear();
        if (perms.isEmpty()){
            txtPlaintext.setText("[Sorry, no solutions were found. Try reducing the dictionary proportion.]");
        }else{
            txtPlaintext.setText(crackEngine.decrypt((char[]) perms.get(0)));
            Iterator it = perms.iterator();
            while (it.hasNext()){
                char[] perm = (char[]) it.next();
                //perm = fillGaps(perm, crackEngine);
                altSolModel.addElement(crackEngine.decrypt(perm));
            }
            altSolList.setSelectedIndex(0);
        }
        //Update statistics.
        lblEstimatedScore.setText("" + crackEngine.findScoreEstimate());
        lblActualScore.setText("" + crackEngine.getBestScore());
        lblIndexCoincidence.setText("" + crackEngine.getIOC());
        lblLanguage.setText(crackEngine.getLanguage());
        lblTime.setText(convertTimeToString(timeTaken,true));
        tabbedPane.setSelectedComponent(outputTab);
    }
    
    /**
     * Clear the output tab.
     */
    private void clearOutput()
    {
        txtPlaintext.setText("");
        altSolModel.clear();
    }
    
    /**
     * Tests whether score estimation was used.
     */
    private boolean usingScoreEstimation(Crack crackEngine)
    {
        return crackEngine.usingScoreEstimation();
    }
    
    /**
     * Tests whether all the content panes are empty.
     */
    private boolean isContentLess()
    {
        boolean status = true;
        status = (txtCiphertext.getText().equals(""));
        status = (txtPlaintext.getText().equals(""));
        status = (logModel.isEmpty());
        status = (altSolModel.isEmpty());
        status = (txtLoadFile.getText().equals(""));
        return status;
    }
}